/*
 *
 * Copyright (c) 2020 The Raptor Authors. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

#include "raptor-lite/utils/thread.h"
#include <string.h>
#ifdef _WIN32
#include <windows.h>
#else
#include <pthread.h>
#include <unistd.h>
#endif

#include "raptor-lite/utils/log.h"
#include "raptor-lite/utils/sync.h"
#include "raptor-lite/utils/useful.h"

namespace raptor {
#ifdef _WIN32
constexpr DWORD MS_VC_EXCEPTION = 0x406D1388;

#pragma pack(push, 8)
typedef struct tagTHREADNAME_INFO {
    DWORD dwType;     // Must be 0x1000.
    LPCSTR szName;    // Pointer to name (in user addr space).
    DWORD dwThreadID; // Thread ID (-1=caller thread).
    DWORD dwFlags;    // Reserved for future use, must be zero.
} THREADNAME_INFO;
#pragma pack(pop)

static void SetThreadName(DWORD dwThreadID, char *threadName) {
    THREADNAME_INFO info;
    info.dwType = 0x1000;
    info.szName = threadName;
    info.dwThreadID = dwThreadID;
    info.dwFlags = 0;

    __try {
        RaiseException(MS_VC_EXCEPTION, 0, sizeof(info) / sizeof(ULONG_PTR), (ULONG_PTR *)&info);
    } __except (EXCEPTION_EXECUTE_HANDLER) {
    }
}
#endif // _WIN32
class InternalThreadImpl;
namespace {
struct ThreadImplArgs {
    InternalThreadImpl *thd;
    Thread::Callback thread_proc;
    void *arg;
    bool joinable;
#ifdef _WIN32
    HANDLE join_object;
#else
    pthread_t join_object;
#endif
    char name[16];
};
} // namespace

class InternalThreadImpl : public ThreadInterface {
public:
    InternalThreadImpl(const char *name, Thread::Callback thread_proc, void *arg, bool *success,
                       const Thread::Options &options)
        : _started(false) {

        _join_object = 0;
        RaptorMutexInit(&_mutex);
        RaptorCondVarInit(&_ready);

        ThreadImplArgs *info = new ThreadImplArgs;
        info->thd = this;
        info->thread_proc = thread_proc;
        info->arg = arg;
        info->joinable = options.Joinable();
        size_t len = strlen(name);
        if (len > sizeof(info->name) - 1) {
            len = sizeof(info->name) - 1;
        }
        if (len > 0) {
            memcpy(info->name, name, len);
        }
        info->name[len] = '\0';
#ifdef _WIN32
        info->join_object = NULL;
        if (options.Joinable()) {
            info->join_object = CreateEvent(NULL, FALSE, FALSE, NULL);
            if (info->join_object == NULL) {
                delete info;
                *success = false;
                return;
            }
        }

        size_t dwStackSize = (options.StackSize() == 0) ? (64 * 1024) : options.StackSize();
        HANDLE handle = CreateThread(NULL, dwStackSize, Run, info, 0, NULL);
        if (handle == NULL) {
            CloseHandle(info->join_object);
            *success = false;
        } else {
            CloseHandle(handle);
            *success = true;
        }
#else
        pthread_attr_t attr;
        pthread_attr_init(&attr);
        pthread_attr_setdetachstate(&attr, options.Joinable() ? PTHREAD_CREATE_JOINABLE
                                                              : PTHREAD_CREATE_DETACHED);
        if (options.StackSize() != 0) {
            pthread_attr_setstacksize(&attr, options.StackSize());
        }
        *success = pthread_create(&info->join_object, &attr, Run, info) == 0;
        pthread_attr_destroy(&attr);
#endif

        if (*success) {
            _join_object = info->join_object;
        } else {
            delete info;
        }
    }

    ~InternalThreadImpl() {
        RaptorMutexDestroy(&_mutex);
        RaptorCondVarDestroy(&_ready);
    }

    void Start() {
        RaptorMutexLock(&_mutex);
        _started = true;
        RaptorCondVarSignal(&_ready);
        RaptorMutexUnlock(&_mutex);
    }

    void Join() {
        if (_join_object != 0) {
#ifdef _WIN32
            WaitForSingleObject(_join_object, INFINITE);
            CloseHandle(_join_object);
#else
            pthread_join(_join_object, nullptr);
#endif
        }
    }

private:
#ifdef _WIN32
    static DWORD WINAPI Run(void *ptr) {
#else
    static void *Run(void *ptr) {
#endif
        ThreadImplArgs info = *(ThreadImplArgs *)ptr;
        delete (ThreadImplArgs *)ptr;
#ifdef _WIN32
        // Systems newer than Win10 1607 or WinServer2016
        // can use API 'SetThreadDescription'
        SetThreadName(GetCurrentThreadId(), info.name);
#else
        pthread_setname_np(info.join_object, info.name);
#endif
        RaptorMutexLock(&info.thd->_mutex);
        while (!info.thd->_started) {
            RaptorCondVarWait(&info.thd->_ready, &info.thd->_mutex, -1);
        }
        RaptorMutexUnlock(&info.thd->_mutex);

        try {
            (info.thread_proc)(info.arg);
        } catch (...) {
            log_error("An exception occurred in %s thread function", info.name);
        }

        if (info.joinable) {
#ifdef _WIN32
            SetEvent(info.join_object);
#endif
        } else {
            delete info.thd;
        }
        return 0;
    }

private:
    bool _started;
    raptor_mutex_t _mutex;
    raptor_condvar_t _ready;
#ifdef _WIN32
    HANDLE _join_object;
#else
    pthread_t _join_object;
#endif
};

Thread::Thread()
    : _impl(nullptr)
    , _state(kNull) {}

Thread::Thread(const char *thread_name, Thread::Callback thread_proc, void *arg, bool *success,
               const Options &options) {
    bool ret = false;
    _impl = new InternalThreadImpl(thread_name, thread_proc, arg, &ret, options);
    if (!ret) {
        _state = kFailed;
        delete _impl;
        _impl = nullptr;
    } else {
        _state = kAlive;
    }
    if (success) {
        *success = ret;
    }
}

Thread::Thread(Thread &&other)
    : _impl(other._impl)
    , _state(other._state) {
    other._impl = nullptr;
    other._state = kNull;
}

Thread &Thread::operator=(Thread &&other) {
    if (this != &other) {
        _impl = other._impl;
        _state = other._state;
        other._impl = nullptr;
        other._state = kNull;
    }
    return *this;
}

void Thread::Start() {
    if (_impl != nullptr) {
        RAPTOR_ASSERT(_state == kAlive);
        _state = kActive;
        _impl->Start();
    } else {
        RAPTOR_ASSERT(_state == kFailed);
    }
}

void Thread::Join() {
    if (_impl != nullptr) {
        if (_state == kAlive) {
            _state = kActive;
            _impl->Start();
        }
        _impl->Join();
        delete _impl;
        _state = kFinish;
        _impl = nullptr;
    } else {
        RAPTOR_ASSERT(_state == kFailed);
    }
}
} // namespace raptor
